package main;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferStrategy;

import javax.swing.JFrame;

import main.ExampleGraphics2D.GameObject;

import org.apache.log4j.Logger;
import org.dyn4j.dynamics.World;

/**
 *
 * @author manh.vu
 *
 */
public class Ren extends JFrame {
	private static final Logger LOGGER = Logger.getLogger(Ren.class);
	/**
	 * Class Rendering
	 */
	private static final long serialVersionUID = -7517344227077004481L;

	/** The conversion factor from nano to base */
	public static final double NANO_TO_BASE = 1.0e9;

	/** Whether the example is stopped or not */
	protected volatile boolean started;

	/** The number of frames per second */
	protected long FPS;

	/** The dynamics engine */
	protected World world;

	private Dimension size;
	private Canvas canvas;

	public Ren(String title) {
		super(title);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.swing.JFrame#frameInit()
	 */
	@Override
	protected void frameInit() {
		super.frameInit();

		// Set size and position
		this.size = new Dimension(480, 320);
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		this.setSize(size);
		this.setLocation(((screenSize.width - size.width) / 2), ((screenSize.height - size.height) / 2) - 100);

		// Set exit on closing
		this.setDefaultCloseOperation(EXIT_ON_CLOSE);

		// Other setting
		this.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				started = false; // Stop game thread
			}
		});
	}

	protected void initVariable() {
		// Initialize world
		FPS = 80L;
		world = new World();
		started = true;

		// Reset size
		this.getContentPane().addComponentListener(new ComponentAdapter() {
			@Override
			public void componentResized(ComponentEvent e) {
				size = e.getComponent().getSize();
				LOGGER.info(size);

				// Initialize canvas
				if(canvas == null) {
					canvas = new Canvas();
					canvas.setBackground(new Color(0, 25, 0));
					add(canvas);
				}

				canvas.setSize(size);
			}
		});
	}

	/**
	 * Start active rendering the example.
	 * <p>
	 * This should be called after the JFrame has been shown.
	 */
	public void start() {
		// don't allow AWT to paint the canvas since we are
		this.canvas.setIgnoreRepaint(true);
		// enable double buffering (the JFrame has to be
		// visible before this can be done)
		this.canvas.createBufferStrategy(2);
		// run a separate thread to do active rendering
		// because we don't want to do it on the EDT
		Thread thread = new Thread() {
			// Create variable for calculate time for sleeping
			protected long afterTime = 0L;
			protected long timeDiff = 0L;
			protected long sleepTime = 0L;
			protected long overSleepTime = 0L;
			/** The time stamp for the last iteration */
			protected long timeBeacon = 0L;

			@Override
			public void run() {
				// perform an infinite loop stopped
				// render as fast as possible
				while (started) {
					// initialize the beacon time
					timeBeacon = System.nanoTime();

					// get the graphics object to render to
					Graphics2D g = (Graphics2D) canvas.getBufferStrategy().getDrawGraphics();

					// render anything about the Example (will render the World objects)
					render(g);

					// dispose of the graphics object
					g.dispose();

					// blit/flip the buffer
					BufferStrategy strategy = canvas.getBufferStrategy();
					if(!strategy.contentsLost()) {
						strategy.show();
					}

					// Sync the display on some systems.
					// (on Linux, this fixes event queue problems)
					Toolkit.getDefaultToolkit().sync();

					// ---------------------------------------------------------
					// Make a sleep for other threads have change to run -------
					afterTime = System.nanoTime();
					timeDiff = afterTime - timeBeacon;

					// Game Update: update the world with the elapsed time -----
					world.update(timeDiff / NANO_TO_BASE);

					sleepTime = (1000_000_000L / FPS - timeDiff) - overSleepTime; // Calculate sleep time

					if(sleepTime > 0) { // some time left in this cycle
						try {
							Thread.sleep(sleepTime / 1000_000L); // nano -> ms
						}catch (InterruptedException ex) {
							LOGGER.error(ex.getLocalizedMessage(), ex.getCause());
						}
						overSleepTime = System.nanoTime() - afterTime;
					}
					else { // sleepTime <= 0; the frame took longer than the period
						Thread.yield(); // give another thread a chance to run
						overSleepTime = 0L;
					}
					// ---------------------------------------------------------
				}
			}
		};
		// set the game loop thread to a daemon thread so that
		// it cannot stop the JVM from exiting
		thread.setDaemon(true);
		// start the game loop
		thread.start();
	}

	/**
	 * Renders the example.
	 *
	 * @param g
	 *            the graphics object to render to
	 */
	protected void render(Graphics2D g) {
		// before we render everything I'm going to flip the y axis and move the
		// origin to the center (instead of it being in the top left corner)
		AffineTransform yFlip = AffineTransform.getScaleInstance(1, -1);
		AffineTransform move = AffineTransform.getTranslateInstance(size.width, -size.height);
		g.transform(yFlip);
		g.transform(move);

		// now (0, 0) is in the center of the screen with the positive x axis
		// pointing right and the positive y axis pointing up

		// lets draw over everything with a white background
		g.setColor(Color.WHITE);
		g.fillRect(-size.width / 2, -size.height / 2, size.width, size.height);

		// lets move the view up some
		// g.translate(0.0, -1.0 * SCALE);

		// draw all the objects in the world
		for(int i = 0; i < world.getBodyCount(); i++) {
			// get the object
			GameObject go = (GameObject) this.world.getBody(i);
			// draw the object
			go.render(g);
		}
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		LOGGER.info("Initialize " + Ren.class.getCanonicalName());
		Ren ren = new Ren("Hello");
		ren.initVariable();
		ren.setVisible(true);

		try {
			Thread.sleep(50);
		}catch (InterruptedException e) {
			LOGGER.error(e.getLocalizedMessage(), e.getCause());
		}

		ren.start();
	}
}
